package jeconbond.experiment.market.equilibrium.generate;

import jeconbond.automata.INode;
import jeconbond.automata.IOutcome;
import jeconbond.automata.AutomataUtils;
import jeconbond.automata.AutomataUtils.IProfCostManagerFactory;
import jeconbond.automata.AutomataUtils.IProfitCostGenManager;
import jeconbond.experiment.generate.IAutomataGenerator;
import jeconbond.economic.resources.IResourceType;
import jeconbond.economic.resources.ResourceBasket;
import jeconbond.experiment.process.BaseJEBExperimentContext;
import jeconbond.experiment.process.IJEBExperimentContext;
import jeconbond.experiment.market.equilibrium.MooreExperimentSettings;
import jeconbond.experiment.market.equilibrium.process.IMooreExperimentContext;

import java.util.List;

import laboratoryQ.computation.IRandom;
import laboratoryQ.BaseUtils;

public class MooreAutomataGenerator implements IAutomataGenerator {
	public static final IProfCostManagerFactory MANAGER_FACTORY_INSTANCE = new ProfitCostManager(null);

	@Override
	public INode[] generate(IJEBExperimentContext iexperimentContext) {
		IMooreExperimentContext experimentContext =
				(IMooreExperimentContext) iexperimentContext;
		MooreExperimentSettings settings = experimentContext.getExperimentSettings();
		INode[] automatas = new INode[settings.agentsCount];
		for (int i = 0; i < settings.agentsCount; i++) {
			INode root = AutomataUtils.generateChain(
				settings.chainLength,
				"Ra" + i + ":",
				settings.profitCostManagerFactory.newManager(experimentContext)
			);
			automatas[i] = root;
		}
		return automatas;
	}

	private static class ProfitCostManager implements IProfitCostGenManager, IProfCostManagerFactory {
		private BaseJEBExperimentContext context;

		public ProfitCostManager(BaseJEBExperimentContext context) {
			this.context = context;
		}

		@Override
		public double addRandomCost(INode[] automatas, IResourceType resource) {
			IRandom random = context.getRandom();
			IOutcome out = getRandOut(automatas, random, false);
			ResourceBasket[] newRq = prolongResBask(out.getRequirements(), resource, random);
			out.setRequirements(newRq);
			return newRq[newRq.length - 1].value;
		}
	
		@Override
		public double addRandomProfit(INode[] automatas, IResourceType resource) {
			IRandom random = context.getRandom();
			IOutcome out = getRandOut(automatas, random, true);
			ResourceBasket[] newPR = prolongResBask(out.getProducedResources(), resource, random);
			out.setProducedResources(newPR);
			return newPR[newPR.length - 1].value;
		}
		
		@Override
		public ResourceBasket[] generateCosts() {
			return AutomataUtils.defaultGenerateRandomBasket(context);
		}
	
		@Override
		public ResourceBasket[] generateProfit() {
			return AutomataUtils.defaultGenerateRandomBasket(context);
		}

		@Override
		public IProfitCostGenManager newManager(IJEBExperimentContext experimentContext) {
			return new ProfitCostManager(
					(BaseJEBExperimentContext) experimentContext
			);
		}

		@Override
		public void onBranchCompleted() { }

		private ResourceBasket[] prolongResBask(
				ResourceBasket[] oldPR,
				IResourceType resource,
				IRandom random
		) {
			ResourceBasket[] newPR = new ResourceBasket[oldPR.length + 1];
			System.arraycopy(oldPR, 0, newPR, 0, oldPR.length);
			double value = random.get();
			newPR[oldPR.length] = new ResourceBasket(resource, value);
			return newPR;
		}
	
		private IOutcome getRandOut(
				INode[] automatas,
				IRandom random,
				boolean onlyWithProfit 
		) {
			INode automata = automatas[BaseUtils.randomInt(random, automatas.length)];
			List<IOutcome> outList = AutomataUtils.dfsGetAllOutcomes(automata);
			if (onlyWithProfit) {
				AutomataUtils.filterOutcomesWithProfit(outList);
			}
			IOutcome out = outList.get(
					BaseUtils.randomInt(random, outList.size())
			);
			return out;
		}
	}
}
